/*
 * Copyright (C) 2015-2021 Alibaba Group Holding Limited
 */

#include <stdio.h>
#include <stdlib.h>
#include <ulog/ulog.h>
#include <dirent.h>
#include <string.h>
#include "record.h"
#include "cx_debug.h"
#include "cx_record.h"
#include "pthread.h"
#include "cx_hal.h"
#include "cx_utility.h"

#define TAG "cxrecord"

#ifndef CX_VR_LOWEST_STORAGE
#define CX_VR_LOWEST_STORAGE    10
#endif

#ifndef CX_VR_MEDIUM_STORAGE
#define CX_VR_MEDIUM_STORAGE    20
#endif

#ifndef CX_VR_STORAGE_CHECK_CYCLE
#define CX_VR_STORAGE_CHECK_CYCLE    3600
#endif

typedef struct {
    cx_slist_t next;
    char *file_name;
} file_node_t;

static cx_slist_t vid_folder_list;
static cx_slist_t pic_folder_list;

static void *storage_manage_tsk(void *arg);
static int read_folder_list(cx_slist_t *folder_list, const char *path);
static file_node_t *get_oldest_folder(cx_slist_t *folder_list);
static void delete_oldest_folder(cx_slist_t *folder_list);
static void free_file_node(file_node_t *node);
static int folder_name_bigger(const char *folder1, const char *folder2);

int cx_vr_start_stmanager(void *hdl)
{
    CHECK_PARAM(hdl, -1);
    cx_record_priv_t *priv = (cx_record_priv_t *)hdl;
    pthread_t thread;
    pthread_attr_t thread_attr;
    struct sched_param shparam = {DEFAULT_THREAD_PRIORITY - 28, 10};

    pthread_attr_init(&thread_attr);
    pthread_attr_setschedparam(&thread_attr, &shparam);
    return pthread_create(&thread, &thread_attr, storage_manage_tsk, priv->root_path);
}

/**
 * delete video or picture folder whichever older
 */
static int delete_oldest_vp_folder(char *v_path, char *p_path)
{
    char *del_path;
    char vid_path[256] = {0};
    char pic_path[256] = {0};
    file_node_t *vid_folder;
    file_node_t *pic_folder;
    
    vid_folder = get_oldest_folder(&vid_folder_list);
    pic_folder = get_oldest_folder(&pic_folder_list);

    if (vid_folder) {
        snprintf(vid_path, 256, "%s/%s", v_path, vid_folder->file_name);
    }

    if (pic_folder) {
        snprintf(pic_path, 256, "%s/%s", p_path, pic_folder->file_name);
    }

    if (!vid_folder) {
        del_path = pic_path[0] ? pic_path : NULL;
        delete_oldest_folder(&pic_folder_list);
    } else if (!pic_folder) {
        del_path = vid_path[0] ? vid_path : NULL;
        delete_oldest_folder(&vid_folder_list);
    } else {
        del_path = folder_name_bigger(vid_folder->file_name, pic_folder->file_name) ? pic_path : vid_path;
    }

    if (del_path == vid_path) {
        delete_oldest_folder(&vid_folder_list);
    } else if (del_path == pic_path) {
        delete_oldest_folder(&pic_folder_list);
    }

    if (del_path) {
        LOGD(TAG, "recycle del folder %s", del_path);
        cx_util_force_rmdir(del_path);
        return 0;
    }
    
    return -1;
}

static void *storage_manage_tsk(void *arg)
{
    struct statfs fsinfo;
    int ret;
    char *root_path = (char *)arg;
    int free_percent;
    char vid_path[256] = {0};
    char pic_path[256] = {0};

    snprintf(vid_path, 256, "%s/%s", root_path, "video");
    snprintf(pic_path, 256, "%s/%s", root_path, "picture");

    cx_sleep_ms(5000);
    LOGD(TAG, "storage_manage_tsk start");

#if (CX_VR_LOWEST_STORAGE < CX_VR_MEDIUM_STORAGE)
    while (1) {
        ret = statfs(root_path, &fsinfo);
        if (ret == 0) {
            LOGD(TAG, "storage total_blocks=%ld available_blocks=%ld", fsinfo.f_blocks, fsinfo.f_bfree);
            
            free_percent = (int)((double)fsinfo.f_bfree * 100 / fsinfo.f_blocks);
            if (free_percent < CX_VR_LOWEST_STORAGE) {
                read_folder_list(&vid_folder_list, vid_path);
                read_folder_list(&pic_folder_list, pic_path);

                /* continously delete the oldest folder until enough space or no folder left under video/ picture/ */
                while (free_percent < CX_VR_MEDIUM_STORAGE) {
                    /* need to free storage space */
                    ret = delete_oldest_vp_folder(vid_path, pic_path);
                    if (ret < 0) {
                        break;
                    }
                    
                    ret = statfs(root_path, &fsinfo);
                    if (ret < 0) {
                        LOGE(TAG, "statfs failed");
                        break;
                    }

                    free_percent = (int)((double)fsinfo.f_bfree * 100 / fsinfo.f_blocks);
                }
            }
            
        } else {
            LOGE(TAG, "stat fs failed %d", ret);
        }

#if (CX_VR_STORAGE_CHECK_CYCLE > 0)
        cx_sleep_ms(CX_VR_STORAGE_CHECK_CYCLE * 1000);
#else
        cx_sleep_ms(3600000);
#endif
    }
#else
#warning "CX_VR_MEDIUM_STORAGE shouble be greater than CX_VR_LOWEST_STORAGE!"
#endif
    
    return NULL;
}

static int folder_name_bigger(const char *folder1, const char *folder2)
{
    int i;

    if (!folder1 || !folder2) {
        return folder1 ? 1 : 0; 
    }

    for (i = 0; folder1[i] && folder2[i]; i++) {
        if (folder1[i] != folder2[i]) {
            return folder1[i] > folder2[i];
        }
    }

    return folder1[i] > folder2[i];
}

static int read_folder_list(cx_slist_t *folder_list, const char *path)
{
    CHECK_PARAM(folder_list && path, -1);

    file_node_t *node_sub;
    cx_slist_t *tmp;
    DIR *dir;
    struct dirent *dirent;

    /* clear the list first */
    cx_slist_for_each_entry_safe(folder_list, tmp, node_sub, file_node_t, next) {
        cx_slist_del(&node_sub->next, folder_list);
        free_file_node(node_sub);
    }

    dir = opendir(path);
    CHECK_RET_WITH_RET(dir, -1);

    while (1) {
        dirent = readdir(dir);
        if(!dirent){
            break;
        }

        /* insert the node to the list from small to big */
        int found = 0;
        cx_slist_t *last_node = folder_list;
        file_node_t *tmp = (file_node_t *)malloc(sizeof(file_node_t));
        tmp->file_name = strdup(dirent->d_name);

        cx_slist_for_each_entry(folder_list, node_sub, file_node_t, next) {
            if (folder_name_bigger(node_sub->file_name, tmp->file_name)) {
                cx_slist_add(&tmp->next, last_node);
                found = 1;
                break;
            }

            last_node = last_node->next;
        }

        if (!found) {
            cx_slist_add_tail(&tmp->next, folder_list);
        }
	}

    closedir(dir);
    return 0;
}

static file_node_t *get_oldest_folder(cx_slist_t *folder_list)
{
    if (!folder_list || !folder_list->next) {
        return NULL;
    }

    /* get the first (oldest) folder from the list */
    return cx_container_of(folder_list->next, file_node_t, next);
}

static void delete_oldest_folder(cx_slist_t *folder_list)
{
    file_node_t *del_node;

    if (folder_list && folder_list->next) {
        del_node = cx_container_of(folder_list->next, file_node_t, next);
        folder_list->next = folder_list->next->next;
        free_file_node(del_node);
    }
}

static void free_file_node(file_node_t *node)
{
    if (node) {
        if (node->file_name) {
            free(node->file_name);
        }

        free(node);
    }
}
